﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace HlgSiemensCommunication
{
    public class SiemensS7Client
    {
        private TcpClient _tcpClient = null;

        private bool _IsLongConnect { get; set; } = false;

        private byte[] command1 = new byte[22]
        {
           0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,0x00,0x01,0x00,0xC0,0x01,0x0A,0xC1,0x02,
           0x01,0x02,0xC2,0x02,0x01,0x00
        };

        private byte[] command2 = new byte[25]
        {
           0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,0x01,0x00,0x00,0x04,0x00,0x00,0x08,0x00,
           0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x01,0xE0
        };

        public int _pud = 0;

        string _ip = "127.0.0.1";

        int _port = 102;

        public bool IsConnection { get; set; } = false;

        public SiemensS7Client(string ip, int port = 102, SiemensTypeS siemens = SiemensTypeS.S1500)
        {
            _ip = ip;
            _port = port;


            switch (siemens)
            {
                case SiemensTypeS.S1200: command1[21] = 0; break;
                case SiemensTypeS.S300: command1[21] = 2; break;
                case SiemensTypeS.S400:
                    command1[21] = 3;
                    command1[17] = 0x00; break;
                case SiemensTypeS.S1500: command1[21] = 0; break;
                default: command1[18] = 0; break;
            }
        }


        public void Conn()
        {
            try
            {
                InitConn();
                _IsLongConnect = true;
            }
            catch (Exception ex)
            {
                IsConnection = false;
                throw new Exception(ex.Message);
            }
        }

        void InitConn()
        {
            try
            {
                if (_tcpClient == null || !_tcpClient.Connected)
                {
                    SiemensCommand.SafeClose(_tcpClient?.Client);

                    _tcpClient = new TcpClient();
                    _tcpClient.SendTimeout = 1500;
                    _tcpClient.ReceiveTimeout = 1500;

                    IAsyncResult connectResult = _tcpClient.BeginConnect(_ip, _port, null, null);
                    //阻塞当前线程           
                    if (!connectResult.AsyncWaitHandle.WaitOne(_tcpClient.SendTimeout))
                        throw new TimeoutException("连接超时");
                    _tcpClient.EndConnect(connectResult);

                    byte[] recommand1 = new byte[1024];
                    byte[] recommand2 = new byte[1024];

                    _tcpClient.Client.Send(command1);
                    _tcpClient.Client.Receive(recommand1);

                    _tcpClient.Client.Send(command2);
                    _tcpClient.Client.Receive(recommand2);
                    var repud = recommand2.Skip(25).Reverse().ToArray();
                    _pud = BitConverter.ToUInt16(repud, 0);

                    IsConnection = true;
                }
            }
            catch (Exception ex)
            {
                IsConnection = false;
                Console.WriteLine(ex.Message);
            }
        }

        public void Close()
        {
            if (_tcpClient != null && _tcpClient.Connected)
            {
                SiemensCommand.SafeClose(_tcpClient?.Client);

                _tcpClient = null;
                IsConnection = false;
            }
        }

        public List<OutputSiemensParams> ReadValue(List<InputSiemensParams> siemensParams)
        {
            List<OutputSiemensParams> outputSiemens = new List<OutputSiemensParams>();
            outputSiemens.AddRange(siemensParams.Select(x => SiemensCommand.MapTo<InputSiemensParams, OutputSiemensParams>(x)));

            var GetReadCommand = new SiemensCommand().GetReadCommand(siemensParams, out List<SiemensParams> outSiemensParams);
            var outputReadByte = GetRecevieBySendPLC(GetReadCommand);

            if (outputReadByte == null || outputReadByte.Length == 0)
                return new List<OutputSiemensParams>();

            //读取数据长度
            var Datalength = BitConverter.ToUInt16(new byte[] { outputReadByte[15], outputReadByte[16] }.Reverse().ToArray(), 0);
            //错误个数
            var error = BitConverter.ToUInt16(new byte[] { outputReadByte[17], outputReadByte[18] }.Reverse().ToArray(), 0);
            //读取总个数
            var ItemCount = Convert.ToUInt16(outputReadByte[20]);
            //去掉头部结果数据
            var resulut = outputReadByte.Skip(21).ToArray();

            resulut = ParseByteValue(outputSiemens, outSiemensParams, ItemCount, resulut);
            return outputSiemens;
        }

        /// <summary>
        /// 从字节中获取对应数据
        /// </summary>
        /// <param name="siemensParams"></param>
        /// <param name="ItemCount"></param>
        /// <param name="resulut"></param>
        /// <returns></returns>
        private byte[] ParseByteValue(List<OutputSiemensParams> outputSiemens, List<SiemensParams> siemensParams, ushort ItemCount, byte[] resulut)
        {
            for (int i = 0; i < ItemCount; i++)
            {
                if (resulut[0] == 0xff && resulut[1] == 3)
                {
                    if (resulut[4] == 0)
                    {
                        outputSiemens[i].Result = false;
                        resulut = resulut.Skip(5).ToArray();
                    }
                    else
                    {
                        resulut = resulut.Skip(5).ToArray();
                        outputSiemens[i].Result = true;
                    }
                }
                else if (resulut[0] == 0 && resulut[1] == 255)
                {
                    resulut = resulut.Skip(1).ToArray();
                    if (resulut[0] == 0xff && resulut[1] == 3)
                    {
                        if (resulut[4] == 0)
                        {
                            resulut = resulut.Skip(5).ToArray();
                            outputSiemens[i].Result = false;
                        }
                        else
                        {
                            resulut = resulut.Skip(5).ToArray();
                            outputSiemens[i].Result = true;
                        }
                    }
                    else if (resulut[0] == 0xff && resulut[1] == 4)
                    {
                        resulut = resulut.Skip(4).ToArray();
                        resulut = ReadValueCallback(outputSiemens, siemensParams, resulut, i);
                    }
                }
                else if (resulut[0] == 0xff && resulut[1] == 4)
                {
                    resulut = resulut.Skip(4).ToArray();
                    resulut = ReadValueCallback(outputSiemens, siemensParams, resulut, i);
                }
                else if (resulut[0] == 0x0a)
                {
                    resulut = resulut.Skip(4).ToArray();
                    outputSiemens[i].Result = "地址出现错误";
                }
                else
                {
                    resulut = resulut.Skip(4).ToArray();
                    outputSiemens[i].Result = "出现未知错误";
                }
            }

            return resulut;
        }

        public List<OutputSiemensParams> WriteValue(List<InputSiemensParams> siemensParams)
        {
            List<OutputSiemensParams> outputSiemens = new List<OutputSiemensParams>();
            outputSiemens.AddRange(siemensParams.Select(x => SiemensCommand.MapTo<InputSiemensParams, OutputSiemensParams>(x)));

            var GetWriteCommand = new SiemensCommand().GetWriteCommand(siemensParams, out List<SiemensParams> outSiemensParams);
            var outputReadByte = GetRecevieBySendPLC(GetWriteCommand);

            if (outputReadByte == null || outputReadByte.Length == 0)
                return new List<OutputSiemensParams>();
            if (outputReadByte.Length == 19)
                return new List<OutputSiemensParams>();

            var Datalength = BitConverter.ToUInt16(new byte[] { outputReadByte[2], outputReadByte[3] }.Reverse().ToArray(), 0);
            var error = BitConverter.ToUInt16(new byte[] { outputReadByte[17], outputReadByte[18] }.Reverse().ToArray(), 0);

            var ItemCount = Convert.ToUInt16(outputReadByte[20]);
            var resulut = outputReadByte.Skip(21).ToArray();

            for (int i = 0; i < ItemCount; i++)
            {
                outputSiemens[i].Result = resulut[i] == 0xff ? true : false;
            }

            return outputSiemens;
        }


        private byte[] ReadValueCallback(List<OutputSiemensParams> outputSiemens, List<SiemensParams> siemensParams, byte[] resulut, int i)
        {
            switch (siemensParams[i].ItemType)
            {
                case SiemensDataType.Bool:

                    break;
                case SiemensDataType.Float:
                    var ofloat = BitConverter.ToSingle(new byte[] { resulut[0], resulut[1], resulut[2], resulut[3] }.Reverse().ToArray(), 0);
                    resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    outputSiemens[i].Result = ofloat;
                    break;
                case SiemensDataType.String:
                    var Ostring = Encoding.Default.GetString(resulut);//.Substring(0, siemensParams[i].Length);
                    resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    outputSiemens[i].Result = Ostring;
                    break;
                case SiemensDataType.Uint16:
                    var ouint16 = BitConverter.ToUInt16(new byte[] { resulut[0], resulut[1] }.Reverse().ToArray(), 0);
                    resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    outputSiemens[i].Result = ouint16;
                    break;
                case SiemensDataType.Int16:
                    var oint16 = BitConverter.ToUInt16(new byte[] { resulut[0], resulut[1] }.Reverse().ToArray(), 0);
                    resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    outputSiemens[i].Result = oint16;
                    break;
                case SiemensDataType.Byte:

                    var obyte = resulut[0];
                    resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    outputSiemens[i].Result = obyte;
                    if (resulut.Length > 0 && resulut[0] != 255)
                        resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    break;
                case SiemensDataType.StringArray:
                    var stringLength = resulut[0];
                    var stringValueLength = Convert.ToUInt16(resulut[1]);
                    var resstr = resulut.Skip(2).ToArray().Take(stringValueLength).ToArray();

                    var OstringArray = Encoding.Default.GetString(resstr);
                    resulut = resulut.Skip(siemensParams[i].Length).ToArray();
                    outputSiemens[i].Result = OstringArray;
                    break;
                default:
                    outputSiemens[i].Result = "未配置对应类型";
                    break;
            }

            return resulut;
        }

        /// <summary>
        /// 同步改为异步方法
        /// </summary>
        /// <param name="inputByte"></param>
        /// <returns></returns>
        byte[] GetRecevieBySendPLC(byte[] inputByte)
        {
            try
            {
                if (!_tcpClient?.Connected ?? false)
                {
                    SiemensCommand.SafeClose(_tcpClient?.Client);
                    Conn();
                }

                lock (this)
                {
                    var obsend = _tcpClient.Client.Send(inputByte);
                    byte[] result = new byte[2048];
                    var endLength = _tcpClient.Client.Receive(result);
                    return result.Take(endLength).ToArray();
                }
            }
            catch (Exception ex)
            {
                SiemensCommand.SafeClose(_tcpClient?.Client);
                Console.WriteLine(ex.Message);
                return null;
            }
        }


        #region Read

        public bool ReadBool(string db, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return Convert.ToBoolean(ReadValue(new List<InputSiemensParams>()
                 {
                    new InputSiemensParams()
                    {
                       DB=db,
                       ItemType= SiemensDataType.Bool,
                       DBtype=dBtype
                    }
                 })[0].Result);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public byte ReadByte(string db, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return Convert.ToByte(ReadValue(new List<InputSiemensParams>()
                {
                   new InputSiemensParams()
                   {
                      DB=db,
                      ItemType= SiemensDataType.Byte,
                      DBtype=dBtype
                   }
                })[0].Result);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public string ReadString(string db, int strLength, bool isRemoveSpace = false, DBtype dBtype = DBtype.DB)
        {
            try
            {
                var output = ReadValue(new List<InputSiemensParams>()
                            {
                               new InputSiemensParams()
                               {
                                  DB=db,
                                  ItemType= SiemensDataType.String,
                                  Length=strLength,
                                   DBtype=dBtype
                               }
                            })[0].Result;

                if (isRemoveSpace)
                {
                    return new SiemensCommand().GetPLCString(output.ToString());
                }

                return Convert.ToString(output);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 读取PLC 数据类型为  string   
        /// </summary>
        /// <param name="db"></param>
        /// <param name="strLength"></param>
        /// <param name="isRemoveSpace"></param>
        /// <returns></returns>
        public string ReadStringArray(string db, int strLength, DBtype dBtype = DBtype.DB, bool isRemoveSpace = false)
        {
            try
            {
                var output = ReadValue(new List<InputSiemensParams>()
                            {
                               new InputSiemensParams()
                               {
                                  DB=db,
                                  ItemType= SiemensDataType.StringArray,
                                  Length=(strLength+2),
                                  DBtype=dBtype
                               }
                            })[0].Result;

                if (isRemoveSpace)
                {
                    return new SiemensCommand().GetPLCString(output.ToString());
                }

                return Convert.ToString(output);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public float ReadFloat(string db, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return Convert.ToSingle(ReadValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Float,
                  DBtype=dBtype
               }
            })[0].Result);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public ushort ReadUint16(string db, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return Convert.ToUInt16(ReadValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Uint16,
                  DBtype=dBtype
               }
            })[0].Result);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public short ReadInt16(string db, DBtype dBtype = DBtype.DB)
        {
            try
            {
                var output = ReadValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Int16,
                  DBtype=dBtype
               }
            })[0].Result;

                return Convert.ToInt16(output);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        #endregion

        #region Write

        public bool WriteBool(string db, bool input, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return (bool)WriteValue(new List<InputSiemensParams>()
                 {
                    new InputSiemensParams()
                    {
                       DB=db,
                       ItemType= SiemensDataType.Bool,
                       WriteValue=input,
                       DBtype=dBtype
                    }
                 })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool WriteByte(string db, byte input, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return (bool)WriteValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Byte,
                  WriteValue=input,
                  DBtype=dBtype
               }
            })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool WriteString(string db, string input, int strLength, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return (bool)WriteValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.String,
                  Length=strLength,
                  WriteValue=input,
                  DBtype=dBtype
               }
            })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool WriteFloat(string db, float input, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return (bool)WriteValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Float,
                  WriteValue=input,
                  DBtype=dBtype
               }
            })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool WriteUint16(string db, UInt16 input, DBtype dBtype = DBtype.DB)
        {
            try
            {

                return (bool)WriteValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Uint16,
                  WriteValue=input,
                  DBtype=dBtype
               }
            })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool WriteInt16(string db, Int16 input, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return (bool)WriteValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.Int16,
                  WriteValue=input,
                  DBtype=dBtype
               }
            })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool WriteStringArray(string db, string input, int strLength, DBtype dBtype = DBtype.DB)
        {
            try
            {
                return (bool)WriteValue(new List<InputSiemensParams>()
            {
               new InputSiemensParams()
               {
                  DB=db,
                  ItemType= SiemensDataType.StringArray,
                  Length= strLength + 2,
                  WriteValue=input,
                  DBtype=dBtype
               }
            })[0].Result;

            }
            catch (Exception ex)
            {
                return false;
            }
        }

        #endregion


        public void PLCErrorLog(string Name, string Content)
        {
            try
            {
                string Date = DateTime.Now.ToString("yyyy-MM-dd");
                string faData = Name + ":   " + "\r\n" + Content + "\r\n" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\r\n";
                string newPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory + @"PLCErrorLog", Name);

                if (!System.IO.Directory.Exists(newPath))
                {
                    System.IO.Directory.CreateDirectory(newPath);
                    return;
                }

                lock (this)
                {
                    string filePathOne = System.IO.Path.Combine(newPath, Date + ".txt");
                    if (filePathOne == "")
                    {
                        using (StreamWriter sw1 = new StreamWriter(filePathOne))
                            sw1.Write(faData);
                    }
                    else
                    {
                        using (StreamWriter sw1 = File.AppendText(filePathOne))
                            sw1.Write(faData);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}
